package org.xteam.pascal.parser.test;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import org.xteam.parser.runtime.AstList;
import org.xteam.parser.runtime.AstNode;
import org.xteam.parser.runtime.reflect.AstAttributeFeature;
import org.xteam.parser.runtime.reflect.AstFeature;
import org.xteam.parser.runtime.reflect.AstListFeature;
import org.xteam.parser.runtime.reflect.AstNodeFeature;

public class AstTreeModel implements TreeModel {
	
	private static abstract class Node {

		public int size() {
			return 0;
		}

		public Object get(int index) {
			throw new UnsupportedOperationException();
		}

		public int index(Object child) {
			return 0;
		}
		
	}
	
	private static class NodeNode extends Node {

		private AstNode node;

		public NodeNode(AstNode node) {
			this.node = node;
		}
		
		public int size() {
			return node.getNodeType().getAllFeatures().length;
		}
		
		public Object get(int index) {
			AstFeature feature = node.getNodeType().getAllFeatures()[index];
			Object obj = getFeature(feature);
			if (feature instanceof AstAttributeFeature) {
				return new AttrFeatureNode((AstAttributeFeature)feature, obj);
			}
			if (feature instanceof AstNodeFeature) {
				return new NodeFeatureNode((AstNodeFeature)feature, (AstNode)obj);
			}
			return new ListFeatureNode((AstListFeature)feature, (AstList)obj);
		}
		
		private Object getFeature(AstFeature feature) {
			try {
				String name = "get" + feature.getName().substring(0,1).toUpperCase() + feature.getName().substring(1);
				Method m = node.getClass().getMethod(name, new Class[0]);
				return m.invoke(node, new Object[0]);
			} catch (Exception e) {
			}
			return null;
		}

		public String toString() {
			return node.getNodeType().getName();
		}

	}
	
	private static class AttrFeatureNode extends Node {
		
		private AstFeature feature;
		private Object obj;

		public AttrFeatureNode(AstFeature feature, Object obj) {
			this.feature = feature;
			this.obj = obj;
		}

		public String toString() {
			return feature.getName() + " = " + obj.toString();
		}
	}
	
	private static class ListFeatureNode extends Node {
		
		private AstFeature feature;
		private AstList obj;

		public ListFeatureNode(AstListFeature feature, AstList obj) {
			this.feature = feature;
			this.obj = obj;
		}
		
		public int size() {
			return obj != null ? obj.size() : 0;
		}
		
		public Object get(int index) {
			return new NodeNode((AstNode)obj.get(index));
		}

		public String toString() {
			return feature.getName();
		}
	}
	
	private static class NodeFeatureNode extends Node {
		
		private AstNodeFeature feature;
		private AstNode node;

		public NodeFeatureNode(AstNodeFeature feature, AstNode node) {
			this.feature = feature;
			this.node = node;
		}
		
		public int size() {
			return 1;
		}
		
		public Object get(int index) {
			if (node == null)
				return new NullNode();
			return new NodeNode(node);
		}

		public String toString() {
			return feature.getName() + "(" + feature.getType().getName() + ")";
		}
	}
	
	private static class NullNode extends Node {
		
		public String toString() {
			return "null";
		}
	}
	
	private Node root;
	private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>();

	public AstTreeModel(AstNode node) {
		this.root = new NodeNode(node);
	}
	
	@Override
	public void addTreeModelListener(TreeModelListener l) {
		listeners .add(l);
	}
	
	@Override
	public void removeTreeModelListener(TreeModelListener l) {
		listeners.remove(l);
	}
	
	@Override
	public Object getRoot() {
		return root;
	}
	
	@Override
	public int getChildCount(Object parent) {
		return ((Node)parent).size();
	}

	@Override
	public Object getChild(Object parent, int index) {
		return ((Node) parent).get(index);
	}

	@Override
	public int getIndexOfChild(Object parent, Object child) {
		return ((Node) parent).index(child);
	}

	@Override
	public boolean isLeaf(Object node) {
		return ((Node)node).size() == 0;
	}

	@Override
	public void valueForPathChanged(TreePath path, Object newValue) {
		throw new UnsupportedOperationException();
	}

}
